﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Sectors;
using Neat.Mathematics;
using Microsoft.Xna.Framework.Graphics;
using Neat.Graphics;

namespace SectorsDesigner.MapMaker
{
    public class Edge
    {
        public List<Vector2> Vertices;
        public List<int> Indices = new List<int>();
        public Wall Wall;

        public int FirstIndex { get { return Indices[0]; } }
        public int LastIndex { get { return Indices[Indices.Count - 1]; } }

        public Vector2 FirstVertex { get { return Vertices[FirstIndex]; } }
        public Vector2 LastVertex { get { return Vertices[LastIndex]; } }

        public Edge(List<Vector2> vertices, int idx1, int idx2, Wall walldata)
        {
            this.Vertices = vertices;
            this.Indices.Add(idx1);
            this.Indices.Add(idx2);
            this.Wall = walldata;
        }

        public Edge(List<Vector2> vertices, Wall walldata)
        {
            this.Vertices = vertices;
            this.Wall = walldata;
        }

        public void Reverse()
        {
            Indices.Reverse();
        }

        public List<Edge> ToEdges()
        {
            if (Indices.Count < 2) return new List<Edge>();
            List<Edge> result = new List<Edge>();
            for (int p = 1, q = 0; p < Indices.Count; q = p++)
            {
                result.Add(new Edge(Vertices, Indices[p], Indices[q], Wall));
            }
            return result;
        }

        public List<LineSegment> ToSegments()
        {
            List<LineSegment> result = new List<LineSegment>();
            if (Indices.Count < 2) return result;
            //for (int p = Indices.Count - 1, q = 0; q < Indices.Count; p = q++)
            for (int p = 1, q=0; p < Indices.Count; q=p++)
            {
                result.Add(new LineSegment(Vertices[Indices[p]], Vertices[Indices[q]]));
            }
            return result;
        }

        public List<Wall> ToWalls()
        {
            List<Wall> result = new List<Wall>();
            if (Indices.Count < 2) return result;
            for (int p = 1, q = 0; p < Indices.Count; q = p++)
            {
                result.Add(Wall.FromWall(Wall, new LineSegment(Vertices[Indices[p]],
                    Vertices[Indices[q]])));
            }
            return result;
        }

        public bool Contains(Vector2 vertex)
        {
            for (int p = 1, q = 0; p < Indices.Count; q = p++)
            {
                if (GeometryHelper.IsPointOnLine(Vertices[Indices[p]], Vertices[Indices[q]], vertex) == GeometryHelper.PointOnLineStates.PointIsOnTheSegment) return true;
            }
            return false;
        }

        public bool Equals(Edge e)
        {
            if (Indices.Count != e.Indices.Count) return false;
            bool eq = true;
            for (int i = 0; i < Indices.Count; i++) 
                if (Indices[i] != e.Indices[i])
                {
                    eq = false;
                    break;
                }
            if (eq) return true;
            for (int i = 0; i < Indices.Count; i++)
            {
                if (Indices[i] != e.Indices[e.Indices.Count - 1 - i]) return false;
            }
            return true;
        }

        public void Draw(SpriteBatch sb, LineBrush lb, Color co, Vector2 offset)
        {
            for (int p = 1, q = 0; p < Indices.Count; q = p++)
            {
                lb.Draw(sb, Vertices[Indices[p]], Vertices[Indices[q]], co, offset);
                LineSegment ls = new LineSegment(Vertices[Indices[p]], Vertices[Indices[q]]);
                Vector2 d = (ls.EndPos - ls.StartPos) / 2f;
                ls.Turn(-MathHelper.PiOver2);
                Vector2 v = (ls.EndPos - ls.StartPos);
                v.Normalize();
                v *= 10.0f;
                ls.EndPos = ls.StartPos + v;
                ls.Translate(d);
                lb.Draw(sb, ls, co, offset);
            }
        }

        public void Draw(SpriteBatch sb, LineBrush lb, Color co)
        {
            Draw(sb, lb, co, Vector2.Zero);
        }
    }

    public class Mesh
    {
        public List<Edge> Edges;
        public List<int> Indices = new List<int>();

        public int FirstIndex { get { return Indices[0]; } }
        public int LastIndex { get { return Indices[Indices.Count - 1]; } }

        public Edge FirstEdge { get { return Edges[FirstIndex]; } }
        public Edge LastEdge { get { return Edges[LastIndex]; } }

        public Mesh(List<Edge> edges)
        {
            Edges = edges;
        }

        public List<Vector2> ToVertexList()
        {
            return GeneratePolygon(Edges, Indices).GetVerticesCounterClockwise();
        }

        public List<int> VertexIndices
        {
            get
            {
                List<int> vertices = new List<int>();
                foreach (var item in Indices)
                {
                    foreach (var v in Edges[item].Indices)
                    {
                        if (!vertices.Contains(v)) vertices.Add(v);
                    }
                }
                return vertices;
            }
        }

        public static bool IsClosedPolygon(List<Edge> Edges, List<int> Indices)
        {
            Dictionary<int, short> reps = new Dictionary<int, short>();
            foreach (var eindex in Indices)
            {
                foreach (var vindex in Edges[eindex].Indices)
                {
                    if (reps.ContainsKey(vindex)) reps[vindex]++;
                    else reps.Add(vindex, 1);
                }
            }
            foreach (var item in reps)
            {
                if (item.Value != 2) return false;
            }
            return true;
        }

        public void GetHeadAndTail(out int HeadIndex, out int TailIndex)
        {
            GetHeadAndTail(Edges, Indices, out HeadIndex, out TailIndex);
        }

        public static void GetHeadAndTail(List<Edge> Edges, List<int> Indices, out int HeadIndex, out int TailIndex)
        {
            Dictionary<int, short> reps = new Dictionary<int, short>();
            foreach (var eindex in Indices)
            {
                foreach (var vindex in Edges[eindex].Indices)
                {
                    if (reps.ContainsKey(vindex)) reps[vindex]++;
                    else reps.Add(vindex, 1);
                }
            }
            int i = -1, j = -1;
            foreach (var item in reps)
            {
                if (item.Value == 1)
                {
                    if (i < 0) i = item.Key;
                    else if (j < 0) j = item.Key;
                    else throw new Exception("Couldn't find head and tail");
                }
            }
            HeadIndex = i;
            TailIndex = j;
        }

        public static bool SegmentsIntersect(List<Edge> Edges, List<int> Indices)
        {
            List<LineSegment> segs = new List<LineSegment>();
            foreach (var eindex in Indices)
            {
                segs.AddRange(Edges[eindex].ToSegments());
            }
            for (int i = 0; i < segs.Count; i++)
            {
                for (int j = 0; j < segs.Count; j++)
                {
                    if (i != j && !GeometryHelper.IsNaN(segs[i].GetIntersectionPoint(segs[j])))
                        return true;
                }
            }
            return false;
        }

        public static bool HasDuplicateEdges(List<int> Indices)
        {
            foreach (var item in Indices)
            {
                if (Indices.FindAll(o => o == item).Count > 1) return true;
            }
            return false;
        }

        public static Polygon GeneratePolygon(List<Edge> Edges, List<int> Indices)
        {
            if (HasDuplicateEdges(Indices))
                throw new Exception("Polygon has duplicate edges.");
            //if (SegmentsIntersect(Edges, Indices))
            //    throw new Exception("Segments intersect.");
            //if (!IsClosedPolygon(Edges, Indices))
            //    throw new Exception("Edges does not make a closed polygon");

            Polygon p = new Polygon();
            List<LineSegment> segs = new List<LineSegment>();
            foreach (var eindex in Indices)
            {
                segs.AddRange(Edges[eindex].ToSegments());
            }
            
            List<int> Visited = new List<int>();
            Vector2 v0 = segs[0].StartPos;
            Vector2 v1 = v0; // segs[0].EndPos;
            //p.AddVertex(v0);

            while (Visited.Count < segs.Count)
            {
                bool found = false;
                for (int i = 0; i < segs.Count; i++)
                {
                    if (!Visited.Contains(i))
                    {
                        if (segs[i].StartPos == v1)
                        {
                            p.AddVertex(v1);
                            v1 = segs[i].EndPos;
                            Visited.Add(i);
                            found = true;
                            break;
                        }
                        else if (segs[i].EndPos == v1)
                        {
                            p.AddVertex(v1);
                            v1 = segs[i].StartPos;
                            Visited.Add(i);
                            found = true;
                            break;
                        }
                    }
                }
                if (v1 == v0) break;

                if (!found)
                    throw new Exception("Vertex " + GeometryHelper.Vector2String(v1) + " was not found");
            }

            return p;
        }

        public bool AddEdge(int e)
        {
            if (Edges.Count < e) 
                throw new IndexOutOfRangeException("Edge index was out of range");
            
            if (Indices.Contains(e)) return false;

            Indices.Add(e);
            return true;
        }

        public List<LineSegment> ToSegments()
        {
            List<LineSegment> segs = new List<LineSegment>();
            foreach (var item in Edges)
            {
                segs.AddRange(item.ToSegments());
            }
            return segs;
        }
    }

    public class SectorInfo
    {
        public Mesh Mesh;
       string _name = "generic";
        float _floorHeight = -100;
        float _ceilingHeight = 100;
        string _floorTexture = "generic_floor";
        string _ceilingTexture = "generic_ceiling";
        Vector2 _floorUV = Vector2.Zero;
        Vector2 _ceilingUV = Vector2.Zero;
        Sectors.Sector.SectorTypes _sectorType = Sectors.Sector.SectorTypes.Normal;
        float _damagePoints = 0;
        string _lowerSector = "";
        string _upperSector = "";
        float _gravityRate;
        int _idleTime = 5; //update every 5 frames
        float _lightIntensity = 1.00f; //light intensity; diffuse color will be multiplied by it


        public string Name { get { return _name; } set { _name = value; } }
        public float FloorHeight { get { return _floorHeight; } set { _floorHeight = value; } }
        public float CeilingHeight { get { return _ceilingHeight; } set { _ceilingHeight = value; } }
        public string FloorTexture { get { return _floorTexture; } set { _floorTexture = value; } }
        public string CeilingTexture { get { return _ceilingTexture; } set { _ceilingTexture = value; } }
        public Vector2 FloorUV { get { return _floorUV; } set { _floorUV = value; } }
        public Vector2 CeilingUV { get { return _ceilingUV; } set { _ceilingUV = value; } }
        public Sectors.Sector.SectorTypes SectorType { get { return _sectorType; } set { _sectorType = value; } }
        public float DamagePoints { get { return _damagePoints; } set { _damagePoints = value; } }
        public string LowerSector { get { return _lowerSector; } set { _lowerSector = value; } }
        public string UpperSector { get { return _upperSector; } set { _upperSector = value; } }
        public float GravityRate { get { return _gravityRate; } set { _gravityRate = value; } }
        public int IdleTime { get { return _idleTime; } set { _idleTime = value; } }
        public float LightIntensity { get { return _lightIntensity; } set { _lightIntensity = value; } }
             
        public string OnEnterScript;
        public string OnLeaveScript;
        public string IdleScript;

        public SectorInfo()
        { }

        public SectorInfo(float _fh, float _ch, string _ft, string _ct, Vector2 _fuv, Vector2 _cuv, string _us, string _ls, float _dp, int _it, 
            float _li, Sectors.Sector.SectorTypes _st, string _n, List<int> _ei, List<Edge> _edges)
        {
            FloorHeight = _fh;
            CeilingHeight = _ch;
            FloorTexture = _ft;
            CeilingTexture = _ct;
            FloorUV = _fuv;
            CeilingUV = _cuv;
            UpperSector = _us;
            LowerSector = _ls;
            DamagePoints = _dp;
            IdleTime = _it;
            LightIntensity = _li;
            SectorType = _st;
            Name = _n;
            Mesh = new Mesh(_edges);
            Mesh.Indices = _ei;
        }

        public Sector ToSector(World w)
        {
            return new Sector(w,
                FloorHeight,
                CeilingHeight,
                FloorTexture,
                CeilingTexture,
                FloorUV,
                CeilingUV,
                UpperSector,
                LowerSector,
                DamagePoints,
                IdleTime,
                LightIntensity,
                SectorType,
                Name,
                Mesh.ToVertexList());
        }
    }

    public class PolygonColorTexture : Polygon
    {
        public Color Color = Color.White;
        public Texture2D Texture;

        VertexPositionColorTexture[] vs;
        Vector2 UVShift;
        public PolygonColorTexture(Polygon p, Color c, Texture2D t)
            : base(p)
        {
            AutoTriangulate = true;
            Triangulate();
            Color = c;
            Texture = t;
        }

        public void FillBuffer(Vector2 offset)
        {
            Triangulate();
            var t = Triangles;
            vs = new VertexPositionColorTexture[t.Count * 3];
            int i=0;

            Vector2 polyMin, polySize;
            GetPositionAndSize(out polyMin, out polySize);

            if (Texture != null)
            {
                Vector2 d = new Vector2(polySize.X / Texture.Width, polySize.Y / Texture.Height);
                foreach (var item in t)
                {
                    vs[i++] = new VertexPositionColorTexture(new Vector3(item.A + offset, 0), Color, ((item.A - polyMin) / polySize) * d + UVShift);
                    vs[i++] = new VertexPositionColorTexture(new Vector3(item.B + offset, 0), Color, ((item.B - polyMin) / polySize) * d + UVShift);
                    vs[i++] = new VertexPositionColorTexture(new Vector3(item.C + offset, 0), Color, ((item.C - polyMin) / polySize) * d + UVShift);
                }
            }
            else
            foreach (var item in t)
            {
                vs[i++] = new VertexPositionColorTexture(new Vector3(item.A + offset, 0), Color, (item.A - polyMin) / polySize);
                vs[i++] = new VertexPositionColorTexture(new Vector3(item.B + offset, 0), Color, (item.B - polyMin) / polySize);
                vs[i++] = new VertexPositionColorTexture(new Vector3(item.C + offset, 0), Color, (item.C - polyMin) / polySize);
            }
        }

        public void Draw(GraphicsDevice device, Texture2D t, Vector2 uv_shift, Vector2 offset)
        {
            Texture = t;
            UVShift = uv_shift;
            FillBuffer(offset);
            if (Texture != null) device.Textures[0] = Texture;
            device.DrawUserPrimitives(PrimitiveType.TriangleList, vs, 0, vs.Length / 3);
        }
    }
}
